package com.mlming.simpledb.backend.dm.page;

import com.mlming.simpledb.backend.dm.pageCache.PageCache;
import com.mlming.simpledb.backend.utils.Parser;

import java.util.Arrays;

/**
 * 后续普通页:
 * 本项目对于普通数据页的管理就比较简单了。
 * **一个普通页面以一个最开始的 2 字节无符号数，表示这一页的空闲位置的偏移。剩下的部分都是实际存储的数据。**
 * (因为PageSize一共就8K = 1<<13 = 8196, 而2字节无符号数, 其实就是short类型, 范围是 1<<16, 所以完全足以表示该页的偏移量)
 * **所以, 对普通页的管理，基本都是围绕着对 上面说的空闲位置的偏移 FSO（Free Space Offset）进行的。例如向页面插入数据**
 *
 * 以下类, 就是根据上述的定义,
 * 对 普通页的Page对象 的 实际存储数据byte[] data进行一个操作
 */
public class PageXDataHandler {
    private static final short OF_FREE = 0; // 代表着空闲空间参数 的 偏移量
    private static final short OF_DATA = 2; // 代表着实际数据 的 偏移量
    public static final int MAX_FREE_SPACE = PageCache.PAGE_SIZE - OF_DATA; // 最大可以存储的数据数(页大小-开始的两个字节)

    // 初始化并获取普通页的data
    public static byte[] InitData() {
        // 定义一个byte[]用于存放数据, 页大小就是我们之前说的PageCache.PAGE_SIZE
        byte[] data = new byte[PageCache.PAGE_SIZE];
        // 根据上述定义, 前两个字节代表着空闲数据位置的偏移量, 此时没有数据, 但前两个字节是必须占着的, 所以空闲位置偏移量是2
        setFSO(data,OF_DATA);
        // 返回data数据, 让外面调用者来设置到Page里面去
        return data;
    }

    // 通过ofData, 设置FSO
    private static void setFSO(byte[] data, short ofData) {
        System.arraycopy(Parser.short2Byte(ofData),0,data,OF_FREE,OF_DATA);
    }

    // 获取FSO:
    public static short getFSO(Page pg) {
        return getFSO(pg.getData());
    }
    private static short getFSO(byte[] raw) {
        // 前两个字节进行
        return Parser.parseShort(Arrays.copyOfRange(raw, 0, 2));
    }

    // 往该Page里面追加 raw数据, 返回插入位置
    public static short insert(Page page, byte[] raw) {
        page.setDirty(true); // 因为有修改, 所以要设置为脏页
        // 1) 先获取其空闲偏移量
        short offset = getFSO(page);
        // 2) 把raw拷贝到page的data里面的fso位置
        System.arraycopy(raw,0,page.getData(),offset,raw.length);
        // 3) 更新fso
        setFSO(page.getData(), (short) (offset + raw.length));
        // 返回插入位置
        return offset;
    }

    // 获取该Page当前的空闲空间
    public static int getFreeSpace(Page page) {
        return PageCache.PAGE_SIZE - (int) getFSO(page.getData());
    }

    // 往指定Page的指定offset进行随机插入raw(如果原位置有数据, 会覆盖),
    // 如果 插入后的位置 比当前FSO大, 我们直接规定当前的FSO = 当前插入后的位置
    public static void recoverInsert(Page page, byte[] raw, short offset) {
        page.setDirty(true); // 因为有修改, 所以要设置为脏页
        // 1) 获取原FSO
        short fso = getFSO(page);
        // 2) 往Page的data 的 offset位置, 进行raw的写入
        System.arraycopy(raw,0,page.getData(),offset,raw.length);
        // 3) 如果 插入后的位置 比当前FSO大, 说明此时的操作是追加, 我们直接规定当前的FSO = 当前插入后的位置
        short insertPos = (short) (offset + raw.length);
        if(fso < insertPos) {
            setFSO(page.getData(),insertPos);
        }
    }

    // 往指定Page的指定offset进行随机插入raw(如果原位置有数据, 会覆盖),
    // 与recoverInsert不同的是, 这里不会更新fso, 因为是更新, 不是插入, 插入有可能会是追加, FSO会增大
    public static void recoverUpdate(Page page, byte[] raw, short offset) {
        page.setDirty(true); // 因为有修改, 所以要设置为脏页
        System.arraycopy(raw, 0, page.getData(), offset, raw.length);
    }
}
